-
Notifications
You must be signed in to change notification settings - Fork 323
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[PoC] Generate IR definitions with annotation processor #11267
base: develop
Are you sure you want to change the base?
Conversation
engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JModule.java
Outdated
Show resolved
Hide resolved
One of the issues with newly generated Java classes is the way to consume them from existing Scala passes. Preferably we do it in a way that doesn't require changes to the existing Scala code. Once I asked
@hubertp has provided following advice when it comes to pattern matching: package org.enso.compiler.core.ir;
import org.enso.compiler.core.IR;
import org.enso.compiler.core.Identifier;
import scala.Option;
import scala.PartialFunction;
import scala.Tuple2$;
import scala.collection.immutable.List;
import java.util.UUID;
import java.util.function.Function;
public class Foo implements Expression {
private final Integer a;
private final String b;
public Foo(Integer a, String b) {
this.a = a;
this.b = b;
}
public static scala.Option<scala.Tuple2<Integer, String>> unapply(Object x) {
if (x instanceof Foo f) {
return Option.apply(Tuple2$.MODULE$.apply(f.a, f.b));
} else {
return Option.empty();
}
} and then package org.enso.compiler.core.ir
object TestPattern {
def testMe(x: Object): Unit = {
x match {
case Foo(a, b) =>
println("A " + a + ", B " + b);
case _ =>
println("nope")
}
}
def main(args: Array[String]): Unit = {
testMe(new Foo(1, "foo"))
}
} "works just fine" according to @hubertp. |
Pavel Marek reports a new STANDUP for today (2024-10-21): Progress: - IRProcessor traverser super interfaces and collects all the fields
|
engine/runtime-parser/src/main/java/org/enso/compiler/core/ir/JName.java
Outdated
Show resolved
Hide resolved
All the required Engine tests are green for the revision that migrated Empty to an interface annotated with @IRNode. This was the very first step that proves that such incremental migration is possible, at least for the simplest possible IR element. |
Pavel Marek reports a new STANDUP for today (2024-12-02): Progress: - Migrating
|
Well, this test is failing and the failure is related. Shouldn't the DSL dependency be just |
|
||
interface JBlank extends JName { | ||
static JBlank create() { | ||
return JNameGen.JBlankGen.builder().build(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An alternative to generating subclass is to generate superclass. With such a style we could turn this JBlank
interface into class. That way the Scala case class
would be represented with Java class
making it more 1:1 and simplify migration.
package org.enso.runtime.parser.processor.test.gen.ir.core;
import org.enso.runtime.parser.dsl.IRChild;
import org.enso.runtime.parser.dsl.IRNode;
import org.enso.runtime.parser.processor.test.gen.ir.module.scope.JDefinition;
import scala.collection.immutable.List;
@IRNode
public interface Name extends Expression {
String name();
boolean isMethod();
@Override
JName duplicate(
boolean keepLocations,
boolean keepMetadata,
boolean keepDiagnostics,
boolean keepIdentifiers);
@GenerateIR(interfaces="Name", superclass="Object")
public final Blank extends BlankGen {
public Blank(IdentifiedLocation identifiedLocation, MetadataStorage passData) {
super(identifiedLocation, passData);
}
public String name() { return "_"; }
// copy, duplicate, mapExpressions,
// children, showCode will be generated into BlankGen
}
@GenerateIR(interfaces="Name", superclass="Object")
public final Literal extends LiteralGen {
public Literal(
String name, boolean isMethod, IdentifiedLocation identifiedLocation,
@IRChild(required = false) Name originalName, MetadataStorage passData
) {
super(name, isMethod, identifiedLocation, originalName, passData);
}
// copy, etc. &
// originalName generated in the super class
}
@GenerateIR(interfaces="Name", superclass="Object")
public final Qualified extends QualifiedGen {
public Qualified(@IRChild List<JName> parts, IdentifiedLocation identifiedLocation, MetadataStorage passData) {
super(parts, identifiedLocation, passData);
}
@Override
public String name() {
return parts().map(JName::name).mkString(".");
}
// children generated in superclass and return parts as annotated by @IRChild
}
}
Each of the implementation classes like Blank
or Qualified
defines a constructor. The annotation processor scans the arguments of the constructor and based on that generates:
super
constructor (package private) to delegate to- fields to store values at
- implementation of
copy
,duplicate
,children
,mapExpression
, etc. - the generated methods will be
public final
and returnQualified
and notQualifiedGen
- processor will check the class is
final
- e.g. restricted just like acase class
This is an example of annotation processor that generates a super class.
Example of the generated code: JExpressionGen.java is generated from JExpression.java |
Closes #11498
Pull Request Description
Introduces
runtime-parser-processor
annotation processor that processes interfaces annotated with@IRNode
.Introduces these 3 new sbt projects:
runtime-parser-dsl
andruntime-parser-processor
.Important Notes
Links to the docs:
Examples of used annotations:
To see the generated code, it is enough to
sbt> runtime-parser/compile
. All the tests are inside theruntime-parser-processor-test
project.Checklist
Please ensure that the following checklist has been satisfied before submitting the PR:
runtime-compiler
on develop and on this PR.Scala,
Java,
TypeScript,
and
Rust
style guides. In case you are using a language not listed above, follow the Rust style guide.